/*
 * @(#) $Header$
 *
 * Copyright (C) 2006  Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.baselib.io;

import java.io.DataInput;
import java.io.DataInputStream;
import java.io.FilterInputStream;
import java.io.InputStream;
import ca.forklabs.baselib.util.Bits;

import java.io.IOException;

import static ca.forklabs.baselib.io.Streams.asDataInputStream;

/**
 * Class {@code LittleEndianInputStream} reads from its underlying stream in a
 * little-endian manner, least-significant byte first. This class is an
 * adaptation from the class in:
 * <blockquote>
 * Elliote Rusty Harold,
 * <i><a target="_blank" href="http://www.oreilly.com/catalog/javaio/">Java I/O</a></i> (p.31),
 * O'Reilly, March 1999, ISBN 1-56592-485-1.
 * </blockquote>
 *
 * @author   <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.baselib.io.LittleEndianInputStream">Daniel Léonard</a>
 * @version $Revision$
 * @see   ca.forklabs.baselib.io.LittleEndianOutputStream
 */
public class LittleEndianInputStream extends FilterInputStream implements DataInput {

//---------------------------
// Instance variables
//---------------------------

   /** The underlying data input stream. */
   private DataInputStream data_in;


//---------------------------
// Constructor
//---------------------------

  /**
   * Constructs a {@code LittleEndianinputStream} build on top of the
   * underlying {@link InputStream}.
   * @param   in   the input stream to read from.
   */
   public LittleEndianInputStream(InputStream in) {
      super(null);

      DataInputStream dis = asDataInputStream(in);
      this.setDataOut(dis);

      this.in = dis;
      }


//---------------------------
// Accessors and mutator
//---------------------------

   /**
    * Gets the underlying data input stream.
    * @return   the underlying data input stream.
    */
   protected DataInputStream getDataIn() {
      return this.data_in;
      }

   /**
    * Changes the underlying data input stream.
    * @param   dis   the new underlying data input stream.
    */
   protected void setDataOut(DataInputStream dis) {
      this.data_in = dis;
      }


//---------------------------
// Implemented methods from java.io.DataInput
//---------------------------

   /**
    * Reads a {@code boolean}.
    * @return   the {@code boolean} read.
    * @exception   IOException   if anything goes wrong with I/O.
    */
   @Override
   @SuppressWarnings("resource")
   public boolean readBoolean() throws IOException {
      DataInputStream dis = this.getDataIn();
      boolean z = dis.readBoolean();
      return z;
      }

   /**
    * Reads a {@code byte}.
    * @return   the {@code byte} read.
    * @exception   IOException   if anything goes wrong with I/O.
    */
   @Override
   @SuppressWarnings("resource")
   public byte readByte() throws IOException {
      DataInputStream dis = this.getDataIn();
      byte b = dis.readByte();
      return b;
      }

   /**
    * Reads an {@code unsigned byte}.
    * @return   the {@code unsigned byte} read.
    * @exception   IOException   if anything goes wrong with I/O.
    */
   @Override
   public int readUnsignedByte() throws IOException {
      byte b = this.readByte();
      int i = Bits.unsignedByte(b);
      return i;
      }

   /**
    * Reads a {@code char}.
    * @return   the {@code char} read.
    * @exception   IOException   if anything goes wrong with I/O.
    */
   @Override
   public char readChar() throws IOException {
      byte b2 = this.readByte();
      byte b1 = this.readByte();
      char c = Bits.makeChar(b1, b2);
      return c;
      }

   /**
    * Reads a {@code short}.
    * @return   the {@code short} read.
    * @exception   IOException   if anything goes wrong with I/O.
    */
   @Override
   public short readShort() throws IOException {
      byte b2 = this.readByte();
      byte b1 = this.readByte();
      short s = Bits.makeShort(b1, b2);
      return s;
      }

   /**
    * Reads an {@code unsigned short}.
    * @return   the {@code unsigned short} read.
    * @exception   IOException   if anything goes wrong with I/O.
    */
   @Override
   public int readUnsignedShort() throws IOException {
      short s = this.readShort();
      int i = Bits.unsignedShort(s);
      return i;
      }

   /**
    * Reads an {@code int}.
    * @return   the {@code int} read.
    * @exception   IOException   if anything goes wrong with I/O.
    */
   @Override
   public int readInt() throws IOException {
      byte b4 = this.readByte();
      byte b3 = this.readByte();
      byte b2 = this.readByte();
      byte b1 = this.readByte();
      int i = Bits.makeInt(b1, b2, b3, b4);
      return i;
      }

   /**
    * Reads a {@code long}.
    * @return   the {@code long} read.
    * @exception   IOException   if anything goes wrong with I/O.
    */
   @Override
   public long readLong() throws IOException {
      byte b8 = this.readByte();
      byte b7 = this.readByte();
      byte b6 = this.readByte();
      byte b5 = this.readByte();
      byte b4 = this.readByte();
      byte b3 = this.readByte();
      byte b2 = this.readByte();
      byte b1 = this.readByte();
      long l = Bits.makeLong(b1, b2, b3, b4, b5, b6, b7, b8);
      return l;
      }

   /**
    * Reads a {@code float}.
    * @return   the {@code float} read.
    * @exception   IOException   if anything goes wrong with I/O.
    */
   @Override
   public float readFloat() throws IOException {
      int i = this.readInt();
      float f = Float.intBitsToFloat(i);
      return f;
      }

   /**
    * Reads a {@code double}.
    * @return   the {@code double} read.
    * @exception   IOException   if anything goes wrong with I/O.
    */
   @Override
   public double readDouble() throws IOException {
      long l = this.readLong();
      double d = Double.longBitsToDouble(l);
      return d;
      }

   /**
    * Reads some bytes from an input stream and stores them into the buffer
    * array. For this method, the concept of endian-ness does not exist.
    * @param   data   the buffer array.
    * @param   off   the initial index.
    * @param   len   the number of bytes to read.
    * @exception   IOException   if anything goes wrong with I/O.
    */
   @Override
   @SuppressWarnings("resource")
   public void readFully(byte[] data, int off, int len) throws IOException {
      DataInputStream dis = this.getDataIn();
      dis.readFully(data, off, len);
      }

   /**
    * Reads some bytes from an input stream and stores them into the buffer
    * array. For this method, the concept of endian-ness does not exist.
    * @param   data   the buffer array.
    * @exception   IOException   if anything goes wrong with I/O.
    */
   @Override
   public void readFully(byte[] data) throws IOException {
      int off = 0;
      int len =  data.length;
      this.readFully(data, off, len);
      }

   /**
    * Reads the next line of text from the input stream. For this method, the
    * concept of endian-ness does not exist.
    * @return   the line read.
    * @deprecated   see {@link DataInputStream}.
    * @exception   IOException   if anything goes wrong with I/O.
    */
   @Override
   @Deprecated
   @SuppressWarnings("resource")
   public String readLine() throws IOException {
      DataInputStream dis = this.getDataIn();
      String line = dis.readLine();
      return line;
      }

   /**
    * Reads in a string that has been encoded using a modified UTF-8 format. For
    * this method, the concept of endian-ness does not exist.
    * @return   the string read.
    * @exception   IOException   if anything goes wrong with I/O.
    */
   @Override
   @SuppressWarnings("resource")
   public String readUTF() throws IOException {
      DataInputStream dis = this.getDataIn();
      String line = dis.readUTF();
      return line;
      }

   /**
    * Makes an attempt to skip over {@code n} bytes of data from the input
    * stream, discarding the skipped bytes.
    * @param   n   the number of bytes to skip.
    * @return   the number of bytes skipped.
    * @exception   IOException   if anything goes wrong with I/O.
    */
   @Override
   @SuppressWarnings("resource")
   public int skipBytes(int n) throws IOException {
      DataInputStream dis = this.getDataIn();
      int skipped = dis.skipBytes(n);
      return skipped;
      }

   }
